<template>
  <div class="workflow-container layout-padding">
    <div class="workflow-mask" v-if="state.isShow"></div>
    <div class="layout-padding-auto layout-padding-view workflow-warp">
      <div class="workflow">
        <!-- 顶部工具栏 -->
        <Tool
          ref="toolRef"
          v-if="isReady && !hideTool"
          :_id="_id"
          @tool="onToolClick"
          @change="onToolChange"
        />

        <!-- 左侧导航区 -->
        <div class="workflow-content flex-grow relative flex">
          <div class="workflow-content-absolute flex" style="">
            <div class="workflow-left flex-shrink h100" v-if="!hideTool">
              <el-scrollbar>
                <div
                  ref="leftNavRefs"
                  v-for="val in leftList"
                  :key="val.id"
                  :style="{ height: val.isOpen ? 'auto' : '50px', overflow: 'hidden' }"
                  class="workflow-left-id"
                >
                  <div
                    class="workflow-left-title cursor-pointer select-none"
                    @click="onTitleClick(val)"
                  >
                    <SvgIcon :name="val.icon" class="ml5 mr5" />
                    <span>{{ val.label }}</span>
                    <SvgIcon :name="val.isOpen ? 'ele-ArrowDown' : 'ele-ArrowRight'" />
                  </div>
                  <div
                    class="workflow-left-item select-none"
                    v-for="(v, k) in val.children"
                    :key="k"
                    :data-id="v.id"
                    :data-node-id="v.nodeId"
                    :class="v.className"
                  >
                    <div class="workflow-left-item-icon">
                      <SvgIcon
                        v-if="val.id !== '3'"
                        :name="v.icon"
                        class="workflow-icon-drag font12 pl1 flex-shrink"
                      />
                      <div class="font12 pl5 name" :title="v.label">{{ v.label }}</div>
                      <el-icon
                        class="color-danger ml-auto cursor-pointer flex-shrink"
                        :size="12"
                        v-if="val.id === '3'"
                        @click.stop="onDeleteShortcut(k)"
                      >
                        <Delete />
                      </el-icon>
                    </div>
                  </div>
                  <el-empty
                    :image-size="30"
                    description="暂无快捷方式"
                    v-if="!val.children?.length"
                  />
                </div>
              </el-scrollbar>
            </div>

            <!-- 右侧绘画区 -->
            <el-scrollbar
              class="workflow-right flex-grow h100"
              ref="scrollbarRef"
              @scroll="onScrollbar"
            >
              <div class="scrollbar-coordinate color-primary">
                {{ scrollbarX }}, {{ scrollbarY }}
              </div>
              <div class="workflow-container" ref="workflowRightRef">
                <div
                  v-for="(v, k) in state.jsplumbData.nodeList"
                  :key="v.nodeId"
                  :id="v.nodeId"
                  :data-id="v.id"
                  :data-node-id="v.nodeId"
                  :class="[v.class, { disabled: v.disabled }]"
                  :style="{ left: v.left, top: v.top }"
                  @click="onItemCloneClick(k)"
                  @dblclick="onCurrentNodeDblclick(v)"
                  @contextmenu.prevent="onContextmenu(v, k, $event)"
                >
                  <div
                    class="workflow-right-box"
                    :class="{ 'workflow-right-active': state.jsPlumbNodeIndex === k }"
                  >
                    <div class="workflow-left-item-icon">
                      <SvgIcon :size="16" :name="v.icon" class="workflow-icon-drag" />
                      <div class="font12 name">{{ v.label }}（{{ v.id }}）</div>
                      <SvgIcon
                        v-if="v.approvalUserType === 1 && !hideTool"
                        name="ele-Avatar"
                        class="font12"
                        :class="v.roleId ? 'color-primary' : 'color-danger'"
                      />
                      <SvgIcon
                        v-if="v.approvalUserType === 2 && !hideTool"
                        name="ele-InfoFilled"
                        class="font12"
                        :class="v.roleType ? 'color-primary' : 'color-info'"
                      />
                    </div>
                  </div>
                </div>

                <!-- Ctrl+鼠标拖动选中元素，用于复制流程图的某些节点和线 -->
                <SelectMove
                  :contentRef="() => workflowRightRef"
                  :edit-row="editRow"
                  @on-paste="onPasteNodeAndEdgeEnd"
                />
              </div>
            </el-scrollbar>
          </div>
        </div>
      </div>
    </div>

    <!-- 节点右键菜单 -->
    <Contextmenu
      :dropdown="state.dropdownNode"
      ref="contextmenuNodeRef"
      @current="onCurrentNodeClick"
    />
    <!-- 线右键菜单 -->
    <Contextmenu
      :dropdown="state.dropdownLine"
      ref="contextmenuLineRef"
      @current="onCurrentLineClick"
    />
    <!-- 抽屉表单、线 -->
    <Drawer
      ref="drawerRef"
      @line="setLineOption"
      :ui-form-items="uiFormItems"
      :jsplumb-data="state.jsplumbData"
      @node="setNodeContent"
      :hide-tool="hideTool"
    />

    <!-- 顶部工具栏-帮助弹窗 -->
    <Help ref="helpRef" />
  </div>
</template>

<script setup lang="ts" name="pagesWorkflow">
import type { BusinessUIQueryColumnType, WorkflowDesignRow } from '@/types/custom'
import type { ScrollbarInstance } from 'element-plus'
import Sortable from 'sortablejs'
import {
  defineAsyncComponent,
  reactive,
  nextTick,
  ref,
  watch,
  onMounted,
  unref,
  onUnmounted,
  onActivated
} from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { jsPlumb, type jsPlumbInstance } from 'jsplumb'
import { useTagsViewRoutes } from '@/stores/tagsViewRoutes'
import { leftNavList } from './js/mock'
import {
  jsplumbDefaults,
  jsplumbMakeSource,
  jsplumbMakeTarget,
  jsplumbConnect,
  connectorStyle
} from './js/config'
import { clone, findTree } from 'xe-utils'
import { useWfApi } from '@/api/wf'
import { useEventListener } from '@vueuse/core'
import { themeConfig } from '@/storage'
import { useBaseRedisApi } from '@/api/base/redis'
import { useUserInfo } from '@/stores/userInfo'
import { Delete } from '@element-plus/icons-vue'
import { Local } from '@/utils/storage'

// 引入组件
const Tool = defineAsyncComponent(() => import('./component/tool/index.vue'))
const Contextmenu = defineAsyncComponent(() => import('./component/contextmenu/index.vue'))
const Drawer = defineAsyncComponent(() => import('./component/drawer/index.vue'))
const Help = defineAsyncComponent(() => import('./component/tool/help.vue'))
const SelectMove = defineAsyncComponent(
  () => import('@/views/system/flow/workflow/component/SelectMove/SelectMove.vue')
)

interface Props {
  _id?: string
  hideTool?: boolean
  design?: WorkflowDesignRow
}

const props = defineProps<Props>()

const { login_name } = useUserInfo()
const { WorkFlowDesignSave } = useWfApi()
const { GetBaseRedisCache, SetBaseRedisCache } = useBaseRedisApi()

// 定义变量内容
let editRow: WorkflowDesignRow = {
  edges: [],
  id: '',
  name: '',
  nodes: []
}
const uiFormItems = ref<BusinessUIQueryColumnType[]>([])
const leftNavRefs = ref([])
const scrollbarX = ref(0)
const scrollbarY = ref(0)
const scrollbarRef = ref<ScrollbarInstance>()
const workflowRightRef = ref()
const contextmenuNodeRef = ref()
const contextmenuLineRef = ref()
const drawerRef = ref()
const helpRef = ref()
const toolRef = ref()
const stores = useTagsViewRoutes()
// const {copyText} = commonFunction()
// 是否初始化结束
const isReady = ref(false)
// 数据
const state = reactive<WorkflowState>({
  dropdownNode: { x: '', y: '' },
  dropdownLine: { x: '', y: '' },
  isShow: false,
  jsPlumbNodeIndex: null,
  jsplumbData: {
    nodeList: [],
    lineList: []
  }
})
// jsPlumb
const instance: jsPlumbInstance = jsPlumb.getInstance()
// 左侧
const leftList = ref<WorkflowDesignNode[]>(leftNavList)
// 快捷方式
const shortcutItems = ref<WorkflowDesignNode[]>([])

watch(
  () => shortcutItems.value,
  () => {
    const shortcut = leftList.value.find((f) => f.id === '3')
    if (shortcut) shortcut.children = shortcutItems.value
  },
  { deep: true }
)

// 设置 宽度小于 768，不支持操
const setClientWidth = () => {
  const clientWidth = document.body.clientWidth
  clientWidth < 768 ? (state.isShow = true) : (state.isShow = false)
}

/**
 * @param {Array} nodeIds
 * @param {String} type
 * @param {Number} len
 * 获取不重复得ID 不断加1直到不重复为止
 */
const getNodeID = (nodeIds: (string | undefined)[], type: string, len: number): string => {
  const id = `${type}${len}`
  if (nodeIds.some((s) => s === id)) {
    return getNodeID(nodeIds, type, len + 1)
  } else {
    return id
  }
}
/**
 * 粘贴节点和线
 * @param nodes
 * @param edges
 */
const onPasteNodeAndEdgeEnd = ({
  nodes,
  edges
}: {
  nodes: WorkflowDesignNode[]
  edges: WorkflowDesignEdge[]
}) => {
  nodes.forEach((node) => {
    // 验证初始节点重复
    const isNode = state.jsplumbData.nodeList.some((s) => s.nodeId === node.nodeId)
    // 生成节点id（唯一）
    const node2 = state.jsplumbData.nodeList.filter((f) => f.nodeType === 2).length + 1
    const node3 = state.jsplumbData.nodeList.filter((f) => f.nodeType === 3).length + 1
    const nodeIds = state.jsplumbData.nodeList.map((v) => v.nodeId)
    if (isNode) {
      const oldId = node.nodeId
      node.nodeId = getNodeID(
        nodeIds,
        node.nodeType === 2 ? 'SP_' : node.nodeType === 3 ? 'CS_' : '',
        node.nodeType === 2 ? node2 : node.nodeType === 3 ? node3 : 0
      )
      const targetOldEdges = edges.filter((f) => f.targetId === oldId)
      const sourceOldEdges = edges.filter((f) => f.sourceId === oldId)
      targetOldEdges.forEach((v) => {
        v.targetId = node.nodeId ?? ''
      })
      sourceOldEdges.forEach((v) => {
        v.sourceId = node.nodeId ?? ''
      })
      node.id = node.nodeId
    }
    // 右侧视图内容数组
    state.jsplumbData.nodeList.push(node)
    // 元素加载完毕时
    nextTick(() => {
      if (node.nodeId) {
        // 整个节点作为source或者target
        instance.makeSource(node.nodeId, jsplumbMakeSource)
        // // 整个节点作为source或者target
        // instance.makeTarget(node.nodeId, jsplumbMakeTarget, jsplumbConnect)
        instance.makeTarget(node.nodeId, jsplumbMakeTarget)
        // 设置节点可以拖拽（此处为id值，非class）
        instance.draggable(node.nodeId, {
          containment: 'parent',
          stop: (el: any) => {
            state.jsplumbData.nodeList.forEach((v) => {
              if (v.nodeId === el.el.id) {
                // 节点x, y重新赋值，防止再次从左侧导航中拖拽节点时，x, y恢复默认
                v.left = `${el.pos[0]}px`
                v.top = `${el.pos[1]}px`
              }
            })
          }
        })
      }
    })
  })

  nextTick(() => {
    // 连线
    edges.forEach((v) => {
      v.id = getNodeID(
        state.jsplumbData.lineList.map((v) => v.id),
        'E_',
        state.jsplumbData.lineList.length
      )
      instance.connect(
        {
          source: v.sourceId,
          target: v.targetId,
          label: v.label || v.id
        },
        jsplumbConnect
      )
    })
    console.log(state.jsplumbData.nodeList.length, state.jsplumbData.lineList.length)
  })
}

// 左侧导航-初始化拖动
const initSortable = () => {
  leftNavRefs.value.forEach((v) => {
    Sortable.create(v as HTMLDivElement, {
      group: {
        name: 'vue-next-admin-1',
        pull: 'clone',
        put: false
      },
      animation: 0,
      sort: false,
      draggable: '.workflow-left-item',
      forceFallback: true,
      onEnd: function (evt: any) {
        const { id } = evt.clone.dataset
        const { layerX, layerY, clientX, clientY } = evt.originalEvent
        const el = workflowRightRef.value!
        const { x, y, width, height } = el.getBoundingClientRect()
        if (clientX < x || clientX > width + x || clientY < y || y > y + height) {
          ElMessage.warning('请把节点拖入到画布中')
        } else {
          const item = findTree(leftNavList, (v) => v.id === id)
          if (item && item.item) {
            const node = clone(item.item, true)
            // 生成节点id（唯一）
            const node2 = state.jsplumbData.nodeList.filter((f) => f.nodeType === 2).length + 1
            const node3 = state.jsplumbData.nodeList.filter((f) => f.nodeType === 3).length + 1
            const nodeIds = state.jsplumbData.nodeList.map((v) => v.nodeId)
            // 验证初始节点重复
            const isNode = state.jsplumbData.nodeList.some((s) => s.nodeId === node.nodeId)
            if (item.parent.id === '3') {
              if (isNode) {
                node.nodeId = getNodeID(
                  nodeIds,
                  node.nodeType === 2 ? 'SP_' : node.nodeType === 3 ? 'CS_' : '',
                  node.nodeType === 2 ? node2 : node.nodeType === 3 ? node3 : 0
                )
                ElMessage.warning(`${node.label} ID重复，重新生成为 ${node.nodeId}。`)
              }
            } else if (isNode) {
              ElMessage.warning(`${node.label} 节点只能有一个。`)
              return
            }
            // 获取节点ID
            const nodeId =
              node.nodeId ||
              getNodeID(
                nodeIds,
                node.nodeType === 2 ? 'SP_' : node.nodeType === 3 ? 'CS_' : '',
                node.nodeType === 2 ? node2 : node.nodeType === 3 ? node3 : 0
              )
            // 处理节点数据
            node['id'] = nodeId
            node['nodeId'] = nodeId
            node['top'] = `${layerY - 15}px`
            node['left'] = `${layerX - 40}px`
            node['class'] = node.className
              ? `workflow-right-clone ${node.className}`
              : 'workflow-right-clone'
            // 右侧视图内容数组
            state.jsplumbData.nodeList.push(node)
            // 元素加载完毕时
            nextTick(() => {
              // 整个节点作为source或者target
              instance.makeSource(nodeId, jsplumbMakeSource)
              // // 整个节点作为source或者target
              // instance.makeTarget(nodeId, jsplumbMakeTarget, jsplumbConnect)
              instance.makeTarget(nodeId, jsplumbMakeTarget)
              // 设置节点可以拖拽（此处为id值，非class）
              instance.draggable(nodeId, {
                containment: 'parent',
                stop: (el: any) => {
                  state.jsplumbData.nodeList.forEach((v) => {
                    if (v.nodeId === el.el.id) {
                      // 节点x, y重新赋值，防止再次从左侧导航中拖拽节点时，x, y恢复默认
                      v.left = `${el.pos[0]}px`
                      v.top = `${el.pos[1]}px`
                    }
                  })
                }
              })
            })
          }
        }
      }
    })
  })
}
// 左侧导航-菜单标题点击
const onTitleClick = (val: any) => {
  val.isOpen = !val.isOpen
}
// 右侧内容区-当前项点击
const onItemCloneClick = (k: number) => {
  state.jsPlumbNodeIndex = k
}
// 右侧内容区-当前项右键菜单点击
const onContextmenu = (v: any, k: number, e: MouseEvent) => {
  state.jsPlumbNodeIndex = k
  const { clientX, clientY } = e
  state.dropdownNode.x = clientX
  state.dropdownNode.y = clientY
  v.type = 'node'
  // v.label = ''
  const item = findTree(leftNavList, (item) => {
    return (
      item.id === v.id ||
      (item.nodeType === v.nodeType && item.approvalUserType === v.approvalUserType)
    )
  })
  if (item && item.item) {
    v.form = item.item.form
  } else {
    v.form = []
  }
  contextmenuNodeRef.value.openContextmenu(v)
}
// Node双击
const onCurrentNodeDblclick = (v: any) => {
  v.type = 'node'
  const item = findTree(leftNavList, (item) => {
    return (
      item.id === v.id ||
      (item.nodeType === v.nodeType && item.approvalUserType === v.approvalUserType)
    )
  })
  if (item && item.item) {
    v.form = clone(item.item.form, true)
  } else {
    v.form = []
  }
  v.contextMenuClickId = 1
  onCurrentNodeClick(v)
}
// 右侧内容区-当前项右键菜单点击回调(节点)
const onCurrentNodeClick = (item: any) => {
  const { contextMenuClickId, nodeId } = item
  if (contextMenuClickId === 0) {
    const nodeIndex = state.jsplumbData.nodeList.findIndex((item) => item.nodeId === nodeId)
    state.jsplumbData.nodeList.splice(nodeIndex, 1)
    instance.removeAllEndpoints(nodeId)
    state.jsPlumbNodeIndex = null
  } else if (contextMenuClickId === 1) {
    drawerRef.value.open(item)
  } else if (contextMenuClickId === 99) {
    shortcutItems.value.push(clone(item, true))
    SetBaseRedisCache({
      [`${login_name}_flow_shortcut`]: JSON.stringify(unref(shortcutItems))
    })
  }
}
// 右侧内容区-当前项右键菜单点击回调(线)
const onCurrentLineClick = (item: any, conn: any) => {
  const { contextMenuClickId } = item
  const { endpoints } = conn
  const intercourse: any = []
  endpoints.forEach((v: any) => {
    intercourse.push({
      id: v.element.id,
      innerText: v.element.innerText
    })
  })
  item.contact = `${intercourse[0].innerText} => ${intercourse[1].innerText}`
  if (contextMenuClickId === 0) instance.deleteConnection(conn)
  else if (contextMenuClickId === 1) drawerRef.value.open(item, conn)
}
// 设置线的 label
const setLineOption = (obj: any) => {
  const { sourceId, targetId, label, id } = obj
  const conn = instance
    .getAllConnections()
    .find((f) => f.id === id || (f.sourceId === sourceId && f.targetId === targetId))
  if (conn) {
    conn.setLabel(label || id || '')
    state.jsplumbData.lineList.forEach((v) => {
      if (v.sourceId === sourceId && v.targetId === targetId) {
        v.label = label
        v.id = id
      }
    })
  }
}
// 设置节点内容
const setNodeContent = (option: WorkflowDesignNode) => {
  // 设置节点 label 与 icon
  state.jsplumbData.nodeList.forEach((v) => {
    if (v.nodeId === option.nodeId) {
      v.label = option.label
      v.icon = option.icon
      v.id = option.id
      v.roleId = option.roleId
      v.roleType = option.roleType
      v.roleRelType = option.roleRelType
      v.nodeType = option.nodeType
      v.formSubmitUser = option.formSubmitUser
      v.formEditItemList = option.formEditItemList || []
      v.formEditItmes = option.formEditItemList?.join(',')
      v.approvalUserType = option.approvalUserType
    }
  })
  // 重绘
  nextTick(() => {
    instance.setSuspendDrawing(false, true)
  })
}

// 顶部工具栏-当前项点击
const onToolClick = (fnName: String, row: WorkflowDesignRow) => {
  switch (fnName) {
    case 'help':
      onToolHelp()
      break
    case 'download':
      onToolDownload()
      break
    case 'submit':
      onToolSubmit(row)
      break
    case 'copy':
      onToolCopy(row)
      break
    case 'del':
      onToolDel()
      break
    case 'fullscreen':
      onToolFullscreen()
      break
    case 'connector':
      onConnectorChange()
      break
    case 'view':
      onScrollView()
      break
  }
}

/**
 * 修改线样式
 */
const onConnectorChange = () => {
  onImportDefaults()
  const edges = clone(state.jsplumbData.lineList, true)
  const nodes = clone(state.jsplumbData.nodeList, true)
  // 清除画布
  clearJsplumbData().then(() => {
    // 设置节点和线
    state.jsplumbData = {
      lineList: edges,
      nodeList: nodes
    }
    // 初始化节点、线的连接
    initJsPlumbConnection()
  })
}

// 顶部工具栏-帮助
const onToolHelp = () => {
  nextTick(() => {
    helpRef.value.open()
  })
}
// 顶部工具栏-下载
const onToolDownload = () => {
  const { globalTitle } = themeConfig.value
  const href =
    'data:text/json;charset=utf-8,' +
    encodeURIComponent(JSON.stringify(state.jsplumbData, null, '\t'))
  const aLink = document.createElement('a')
  aLink.setAttribute('href', href)
  aLink.setAttribute('download', `${globalTitle}工作流.json`)
  aLink.click()
  aLink.remove()
  ElMessage.success('下载成功')
}
// 顶部工具栏-提交
const onToolSubmit = (row: WorkflowDesignRow) => {
  row.edges = state.jsplumbData.lineList
  row.nodes = state.jsplumbData.nodeList
  WorkFlowDesignSave(row)
    .then(() => {
      toolRef.value.initGridData()
      ElMessage.success('数据提交成功。')
    })
    .catch(() => {
      ElMessage.warning('数据提交失败。')
    })
}
// 顶部工具栏-粘贴
const onToolCopy = (row: WorkflowDesignRow) => {
  editRow.edges = clone(row.edges || [], true)
  editRow.nodes = clone(row.nodes || [], true)
  // uiFormItems.value = editRow.uiFormItems || []
  // 重置画布
  clearJsplumbData().then(() => {
    // 设置节点和线
    state.jsplumbData = {
      lineList: editRow.edges,
      nodeList: editRow.nodes.map((v) => {
        v.nodeId = v.id
        return v
      })
    }
    // 初始化节点、线的连接
    initJsPlumbConnection()

    ElMessage.success(`粘贴成功 - ${editRow.name || '新增'}`)
  })
}
// 顶部工具栏-删除
const onToolDel = () => {
  ElMessageBox.confirm('此操作将清空画布，是否继续？', '提示', {
    confirmButtonText: '清空',
    cancelButtonText: '取消'
  })
    .then(() => {
      clearJsplumbData().then(() => {
        ElMessage.success('清空画布成功')
      })
    })
    .catch(() => {})
}

/**
 * 清除画布
 */
const clearJsplumbData = () => {
  state.jsplumbData.nodeList = []
  state.jsplumbData.lineList = []
  instance.reset(true)
  return new Promise((resolve) => {
    nextTick(() => {
      resolve(true)
    })
  })
}

// 顶部工具栏-全屏
const onToolFullscreen = () => {
  stores.setCurrenFullscreen(true)
}

/**
 * 加载快捷方式
 */
const initFlowShortcut = () => {
  GetBaseRedisCache(`${login_name}_flow_shortcut`).then(({ data }) => {
    try {
      shortcutItems.value = JSON.parse(data)
    } catch (e) {
      shortcutItems.value = []
    }
  })
}

/**
 * 删除快捷方式
 */
const onDeleteShortcut = (index: number) => {
  shortcutItems.value.splice(index, 1)
  SetBaseRedisCache({
    [`${login_name}_flow_shortcut`]: JSON.stringify(unref(shortcutItems))
  })
}

// 鼠标按下
const isMousedown = ref(false)
// Ctrl按下
const isControl = ref(false)

/**
 * 获取滚动坐标
 */
const onScrollbar = ({ scrollLeft, scrollTop }: { scrollLeft: number; scrollTop: number }) => {
  scrollbarX.value = scrollLeft
  scrollbarY.value = scrollTop
}

/**
 * 回到视图
 */
const onScrollView = () => {
  const minTop = Math.min(...state.jsplumbData.nodeList.map((v) => parseInt(v.top || '0'))) || 0
  const minLeft = Math.min(...state.jsplumbData.nodeList.map((v) => parseInt(v.left || '0'))) || 0
  scrollbarRef.value?.setScrollTop(minTop - 10)
  scrollbarRef.value?.setScrollLeft(minLeft - 10)
}

/**
 * 全局事件
 */
const initEventListener = () => {
  let pageX = 0,
    pageY = 0
  useEventListener(window, 'mouseup', () => {
    isMousedown.value = false
  })
  useEventListener(workflowRightRef, 'mousedown', (event) => {
    if (event.button === 0 && !isControl.value) {
      pageX = event.pageX + scrollbarX.value
      pageY = event.pageY + scrollbarY.value
      isMousedown.value = true
    }
  })
  useEventListener(workflowRightRef, 'mousemove', (event) => {
    if (isMousedown.value && !isControl.value) {
      scrollbarRef.value?.setScrollLeft(pageX - event.pageX)
      scrollbarRef.value?.setScrollTop(pageY - event.pageY)
    }
  })
  useEventListener(document.body, 'keyup', (event) => {
    if (['ControlLeft', 'ControlRight'].includes(event.code)) {
      isControl.value = false
    }
  })
  useEventListener(document.body, 'keydown', (event) => {
    if (['ControlLeft', 'ControlRight'].includes(event.code)) {
      isControl.value = true
    }

    if (isControl.value) {
      if (['KeyF'].includes(event.code)) {
        event.stopPropagation()
        event.preventDefault()
        onScrollView()
      }
      // else if (['KeyV'].includes(event.code)) {
      //   event.stopPropagation()
      //   event.preventDefault()
      //   console.log('粘贴')
      //   toolRef.value?.onToolClick('copy')
      // } else if (['KeyC'].includes(event.code)) {
      //   event.stopPropagation()
      //   event.preventDefault()
      //   console.log('复制')
      //   // toolRef.value?.onToolClick('copy')
      // }
    }
  })
}

/**
 * 选中流程定义
 * @param row
 */
const onToolChange = (row?: WorkflowDesignRow) => {
  if (row) editRow = row
  uiFormItems.value = editRow.uiFormItems || []
  // 重置画布
  clearJsplumbData().then(() => {
    // 设置节点和线
    state.jsplumbData = {
      lineList: editRow.edges,
      nodeList: editRow.nodes.map((v) => {
        v.nodeId = v.id
        return v
      })
    }
    // 初始化节点、线的连接
    initJsPlumbConnection()
  })
}

// 初始化节点、线的链接
const initJsPlumbConnection = () => {
  nextTick(() => {
    // 节点
    state.jsplumbData.nodeList.forEach((v) => {
      const nodeId = String(v.nodeId)
      // 整个节点作为source或者target
      instance.makeSource(nodeId, jsplumbMakeSource)
      // 整个节点作为source或者target
      instance.makeTarget(nodeId, jsplumbMakeTarget)
      // 设置节点可以拖拽（此处为id值，非class）
      instance.draggable(nodeId, {
        containment: 'parent',
        stop: (el: any) => {
          state.jsplumbData.nodeList.forEach((v) => {
            if (v.nodeId === el.el.id) {
              // 节点x, y重新赋值，防止再次从左侧导航中拖拽节点时，x, y恢复默认
              v.left = `${el.pos[0]}px`
              v.top = `${el.pos[1]}px`
            }
          })
        }
      })
    })
    // 线样式
    state.jsplumbData.lineList.forEach((v) => {
      instance.connect(
        {
          source: v.sourceId,
          target: v.targetId,
          label: v.label || v.id
        },
        jsplumbConnect
      )
    })
    onScrollView()
  })
}

/**
 * 画流程事件
 */
const initJsPlumbEvent = () => {
  // 点击线弹出右键菜单
  instance.bind('contextmenu', (conn, event: any) => {
    event.preventDefault()
    const { sourceId, targetId } = conn
    const { clientX, clientY } = event
    state.dropdownLine.x = clientX
    state.dropdownLine.y = clientY
    const line: any = state.jsplumbData.lineList.find(
      (v) => v.sourceId === sourceId && v.targetId === targetId
    )
    contextmenuLineRef.value.openContextmenu(line, conn)
  })
  instance.bind('dblclick', (conn, event: any) => {
    event.preventDefault()
    const { sourceId, targetId } = conn
    const line: any = state.jsplumbData.lineList.find(
      (v) => v.sourceId === sourceId && v.targetId === targetId
    )
    onCurrentLineClick(Object.assign(line, { contextMenuClickId: 1 }), conn)
  })
  // 连线之前
  instance.bind('beforeDrop', (conn) => {
    const { sourceId, targetId } = conn
    const item = state.jsplumbData.lineList.find(
      (v) => v.sourceId === sourceId && v.targetId === targetId
    )
    if (item) {
      ElMessage.warning('关系已存在，不可重复连接')
      return false
    } else {
      return true
    }
  })
  // 连线时
  instance.bind('connection', (conn) => {
    const { sourceId, targetId } = conn
    if (
      !state.jsplumbData.lineList.some((s) => s.targetId === targetId && s.sourceId === sourceId)
    ) {
      const id = getNodeID(
        state.jsplumbData.lineList.map((v) => v.id),
        'E_',
        state.jsplumbData.lineList.length
      )
      const option = {
        id,
        sourceId,
        targetId,
        type: 'line',
        label: ''
      }
      state.jsplumbData.lineList.push(option)
      setTimeout(() => {
        setLineOption(option)
      }, 500)
    }
  })
  // 删除连线时回调函数
  instance.bind('connectionDetached', (conn: any) => {
    const { sourceId, targetId } = conn
    state.jsplumbData.lineList = state.jsplumbData.lineList.filter((line) => {
      return !(line.sourceId == sourceId && line.targetId == targetId)
    })
  })
}

/**
 * 导入默认配置
 */
const onImportDefaults = () => {
  // instance.fire('jsPlumbDemoLoaded', instance)
  const connectorKey = Local.get('Connector') || 'Bezier'
  jsplumbDefaults.Connector = connectorStyle[connectorKey] || jsplumbDefaults.Connector
  instance.importDefaults(jsplumbDefaults)
}

// 初始化 jsPlumb
const initJsPlumb = () => {
  return new Promise((resolve) => {
    // 容器
    instance.setContainer(workflowRightRef.value)

    instance.ready(() => {
      // 初始化左侧拖动
      initSortable()
      // 导入默认配置
      onImportDefaults()
      // 会使整个jsPlumb立即重绘。
      instance.setSuspendDrawing(false, true)
      // 事件
      initJsPlumbEvent()

      isReady.value = true

      resolve(true)
    })
  })
}

// 销毁
const onJsplumbDestroy = () => {
  clearJsplumbData()
  // @ts-ignore: 不验证 destroy是存在的，但是官方class没声明
  instance.destroy()
}

onMounted(() => {
  // 初始化JsPlumb
  initJsPlumb().then(() => {
    if (props.design) {
      onToolChange(props.design)
    }
  })
  // 初始化快捷节点
  initFlowShortcut()
  // 全局事件
  initEventListener()
  // 小屏幕不支持操作
  setClientWidth()
  // 响应式
  useEventListener('resize', setClientWidth)
})

watch(
  () => props.design,
  (value) => {
    if (value) onToolChange(props.design)
  }
)

onActivated(onScrollView)

onUnmounted(onJsplumbDestroy)
</script>
